package com.youlin.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.youlin.dto.BaseDataResDto;
import com.youlin.dto.BaseResDto;
import com.youlin.dto.Status;
import com.youlin.dto.req.*;
import com.youlin.dto.res.*;
import com.youlin.entities.*;
import com.youlin.mapper.BygDeadManMapper;
import com.youlin.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlin.untils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

@Service
public class BygDeadManServiceImpl extends ServiceImpl<BygDeadManMapper, BygDeadMan> implements BygDeadManService {

    private static final ReentrantLock reentrantLock = new ReentrantLock();
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private BygAppointmentService bygAppointmentService;
    @Autowired
    private BygTextTemplateService bygTextTemplateService;
    @Autowired
    private BygBgTemplateService bygBgTemplateService;
    @Autowired
    private BygMusicTemplateService bygMusicTemplateService;
    @Autowired
    private BygPhotoService bygPhotoService;
    @Autowired
    private BygSignInService bygSignInService;
    @Autowired
    private BygAdminService bygAdminService;
    @Autowired
    private BygSignInHistoryService bygSignInHistoryService;
    @Autowired
    private BygSignInTemplateService bygSignInTemplateService;

    @Override
    public BaseDataResDto<DeadManResDto> findDeadManByAmUuId(String amUuId) {
        DeadManResDto deadManRes = RedisUtil.getObject(redisTemplate, "amUuId_dm:" + amUuId, DeadManResDto.class);
        if (deadManRes == null) {
            try {
                reentrantLock.lock();
                if (deadManRes == null) {
                    DeadManResDto finalDmRes = new DeadManResDto();

                    CompletableFuture<BygDeadMan> future0 = CompletableFuture.supplyAsync(() -> {
                        BygAppointment appointment = bygAppointmentService.getOne(new QueryWrapper<BygAppointment>().eq("uu_id", amUuId));
                        if (appointment == null) { // 防止缓存穿透
                            RedisUtil.setObjectExpire(redisTemplate, "amUuId_dm:"+amUuId, new DeadManResDto());
                            throw new RuntimeException(Status.PARAMETERERROR.getMessage());
                        }
                        BygDeadMan deadMan = getById(appointment.getDmId());
                        finalDmRes.setDeadBirthday(deadMan.getBirthday()).setAmId(appointment.getId())
                                .setDeadName(deadMan.getName()).setStatus(appointment.getStatus())
                                .setHeadUrl(deadMan.getHeadUrl()).setDeadDeadday(deadMan.getDeadday())
                                .setDeadGender(deadMan.getGender()).setLifeStory(deadMan.getLifeStory());
                        return deadMan;
                    }, threadPoolExecutor);

                    CompletableFuture<Void> future1 = future0.thenAcceptAsync(deadMan -> {
                        BygMusicTemplate musicTemplate = bygMusicTemplateService.getById(deadMan.getMsId());
                        finalDmRes.setMusicUrl(musicTemplate.getUrl());
                    }, threadPoolExecutor);

                    CompletableFuture<Void> future2 = future0.thenAcceptAsync(deadMan -> {
                        List<BygTextTemplate> textTemplates = bygTextTemplateService
                                .list(new QueryWrapper<BygTextTemplate>().eq("name", deadMan.getTxName()));
                        for (BygTextTemplate tx: textTemplates) {
                            finalDmRes.setTxName(tx.getName());
                            if (tx.getType() == MyEnum.TX_TYPE_HEAD.getValue()) finalDmRes.setHeadTx(tx.getContent());
                            if (tx.getType() == MyEnum.TX_TYPE_LIFE.getValue()) finalDmRes.setLifeStoryTx(tx.getContent());
                        }
                    }, threadPoolExecutor);

                    CompletableFuture<Void> future3 = future0.thenAcceptAsync(deadMan -> {
                        List<BygBgTemplate> bygBgTemplates = bygBgTemplateService.list(Wrappers
                                .lambdaQuery(BygBgTemplate.class).eq(BygBgTemplate::getBgId, deadMan.getBgId()));
                        Map<String, BygBgTemplate> pBrMap = Maps.newHashMap();
                        List<BygBgTemplate> pBgList = Lists.newArrayList();
                        BygBgTemplate siBg = null;
                        for (BygBgTemplate bg: bygBgTemplates) {
                            if (bg.getType() == MyEnum.BG_TYPE_HEAD.getValue()) finalDmRes.setHeadBgUrl(bg.getUrl());
                            if (bg.getType() == MyEnum.BG_TYPE_HEAD_BR.getValue()) finalDmRes.setHeadBrUrl(bg.getUrl());
                            if (bg.getType() == MyEnum.BG_TYPE_LIFE.getValue()) finalDmRes.setLifeStoryBgUrl(bg.getUrl());
                            if (bg.getType() == MyEnum.BG_TYPE_PHOTO_BR.getValue()) pBrMap.put(bg.getWh(), bg);
                            if (bg.getType() == MyEnum.BG_TYPE_PHOTO.getValue()) pBgList.add(bg);
                            if (bg.getType() == MyEnum.BG_TYPE_SI.getValue()) siBg = bg;
                        }
                        Map<Integer, List<BygBgTemplate>> pBgListMap = pBgList.parallelStream().collect(Collectors.groupingBy(BygBgTemplate::getPid));
                        LambdaQueryWrapper<BygPhoto> wrapper = Wrappers.lambdaQuery(BygPhoto.class)
                                .eq(BygPhoto::getDmId, deadMan.getId()).orderByAsc(BygPhoto::getNo);
                        AtomicInteger ai1 = new AtomicInteger(0);
                        AtomicInteger ai2 = new AtomicInteger(0);
                        AtomicInteger ai3 = new AtomicInteger(0);
                        List<PhotoResDto> photoResList = bygPhotoService.list(wrapper).parallelStream().map(p -> {
                            String wh = p.getWh();
                            BygBgTemplate pBr = pBrMap.get(wh);
                            List<BygBgTemplate> ppBgList = pBgListMap.get(pBr.getId());
                            Integer index = null;
                            if (wh.equals("520*700")) index = ai1.getAndIncrement() % ppBgList.size();
                            if (wh.equals("625*415")) index = ai2.getAndIncrement() % ppBgList.size();
                            if (wh.equals("530*620")) index = ai3.getAndIncrement() % ppBgList.size();
                            BygBgTemplate pBg = ppBgList.get(index);
                            return new PhotoResDto().setPhotoUrl(p.getPhotoUrl()).setPhotoBrUrl(pBr.getUrl())
                                    .setRemark(p.getRemark()).setPhotoBgUrl(pBg.getUrl()).setWh(p.getWh());
                        }).collect(Collectors.toList());
                        finalDmRes.setPhotos(photoResList);
                        if (siBg != null) {
                            finalDmRes.setSignInBgUrl(siBg.getUrl());
                            bygBgTemplates.parallelStream()
                                    .filter(bg -> bg.getType() == MyEnum.BG_TYPE_SIFB.getValue())
                                    .findFirst().ifPresent(bg -> finalDmRes.setSignInBrUrl(bg.getUrl()));
                            Map<Integer, String> siflUrls = bygBgTemplates.parallelStream()
                                    .filter(bg -> bg.getType() == MyEnum.BG_TYPE_SIFL.getValue())
                                    .collect(Collectors.toMap(BygBgTemplate::getPid, bg -> bg.getUrl()));
                            Map<Integer, BygBgTemplate> sifMap = bygBgTemplates.parallelStream()
                                    .filter(bg -> bg.getType() == MyEnum.BG_TYPE_SIF.getValue())
                                    .collect(Collectors.toMap(BygBgTemplate::getPid, bg -> bg));
                            List<BgSitResDto> sifs = bygBgTemplates.parallelStream().filter(bg -> bg.getType() == MyEnum.BG_TYPE_SIFH.getValue())
                                    .map(bg -> new BgSitResDto().setHighUrl(bg.getUrl()).setLowUrl(siflUrls.get(bg.getId()))
                                            .setText(bg.getText()).setFlowerUrl(sifMap.get(bg.getId()).getUrl())
                                            .setSitId(Integer.parseInt(sifMap.get(bg.getId()).getText()))).collect(Collectors.toList());
                            finalDmRes.setSifs(sifs);
                        }
                    }, threadPoolExecutor);

                    CompletableFuture<DeadManResDto> future4 = CompletableFuture.allOf(future1, future2, future3).thenApplyAsync(f -> {
                        RedisUtil.setObjectExpire(redisTemplate, "amUuId_dm:" + amUuId, finalDmRes);
                        return finalDmRes;
                    }, threadPoolExecutor);
                    deadManRes = future4.get();
                }
            } catch (Exception e) {
                if (e.getMessage().contains(Status.PARAMETERERROR.getMessage()))
                    return new BaseDataResDto<>(Status.PARAMETERERROR);
                return new BaseDataResDto<>(Status.ERROR);
            } finally {
                reentrantLock.unlock();
            }
        }
        String siId = Md5Util.getUUID();
        Integer amId = deadManRes.getAmId();
        if (deadManRes.getStatus() < MyEnum.AM_REVIEWING.getValue())
            threadPoolExecutor.execute(() -> bygSignInService.save(new BygSignIn().setUuId(siId).setAmId(amId)));
        return new BaseDataResDto<DeadManResDto>(Status.SUCCESS).setData(deadManRes.setSiId(siId));
    }

    @Override
    @Transactional
    public <T> BaseResDto editDeadManData(T data) {
        BygDeadMan deadMan = new BygDeadMan();
        if (data instanceof EditDMDataReqDto) {
            EditDMDataReqDto editDMData = (EditDMDataReqDto) data;
            deadMan.setId(editDMData.getDmId()).setName(editDMData.getName())
                    .setHeadUrl(editDMData.getHeadUrl()).setLifeStory(editDMData.getLifeStory());
        }
        if (data instanceof SetDMBgReqDto) {
            SetDMBgReqDto setDMBg = (SetDMBgReqDto) data;
            deadMan.setId(setDMBg.getDmId()).setBgId(setDMBg.getBgId());
        }
        if (data instanceof SetDMTXReqDto) {
            SetDMTXReqDto setDMTX = (SetDMTXReqDto) data;
            deadMan.setId(setDMTX.getDmId()).setTxName(setDMTX.getTxName());
        }
        if (data instanceof SetDMMsReqDto) {
            SetDMMsReqDto setDMMs = (SetDMMsReqDto) data;
            deadMan.setId(setDMMs.getDmId()).setMsId(setDMMs.getMsId());
        }
        if (! updateById(deadMan.setUpdateTime(LocalDateTime.now()))) return new BaseResDto(Status.ERROR);
        BygAppointment appointment = bygAppointmentService.getOne(
                new QueryWrapper<BygAppointment>().eq("dm_id", deadMan.getId()));
        redisTemplate.delete("amUuId_dm:" + appointment.getUuId());
        return new BaseResDto(Status.SUCCESS);
    }

    @Override
    public BaseDataResDto<DMResDto> findDeadManById(String dmId) {
        BygDeadMan deadMan = getById(dmId);
        if (deadMan == null) return new BaseDataResDto<>(Status.PARAMETERERROR);
        DMResDto dmResDto = BaseService.copyProperties(deadMan, DMResDto.class);
        LambdaQueryWrapper<BygPhoto> wrapper = Wrappers.lambdaQuery(BygPhoto.class)
                .eq(BygPhoto::getDmId, dmId).orderByAsc(BygPhoto::getNo);
        List<PhotoReqDto> photos = bygPhotoService.list(wrapper).parallelStream()
                .map(p -> MyBeanUtil.copyProperties(p, PhotoReqDto.class)).collect(Collectors.toList());
        return new BaseDataResDto<DMResDto>(Status.SUCCESS).setData(dmResDto.setPhotos(photos));
    }

    @Override
    public BaseDataResDto<DmFinallyResDto> findDmByAmUuId(String amUuId, String takerToken) {

        AtomicReference<BygDeadMan> dmar = new AtomicReference<>(null);
        AtomicReference<List<BygBgTemplate>> bgAr = new AtomicReference<>(null);
        AtomicReference<Integer> sitIdAr = new AtomicReference<>(null);
        AtomicReference<PhotoResDto> photoAr = new AtomicReference<>(null);
        AtomicReference<String> telAr = new AtomicReference<>(null);

        CompletableFuture<BygAppointment> future0 = CompletableFuture.supplyAsync(() -> {
            LambdaQueryWrapper<BygAppointment> wrapper = Wrappers
                    .lambdaQuery(BygAppointment.class).eq(BygAppointment::getUuId, amUuId);
            BygAppointment appointment = bygAppointmentService.getOne(wrapper);
            if (appointment == null) throw new RuntimeException(Status.PARAMETERERROR.getMessage());
            dmar.set(getById(appointment.getDmId()));
            LambdaQueryWrapper<BygBgTemplate> bgWrapper = Wrappers.lambdaQuery(BygBgTemplate.class)
                    .eq(BygBgTemplate::getBgId, dmar.get().getBgId())
                    .and(w -> w.ge(BygBgTemplate::getType, MyEnum.BG_TYPE_FAMILY.getValue()).or()
                        .eq(BygBgTemplate::getType, MyEnum.BG_TYPE_PHOTO_BR.getValue()));
            bgAr.set(bygBgTemplateService.list(bgWrapper));
            return appointment;
        }, threadPoolExecutor);

        CompletableFuture<Void> future1 = future0.thenAcceptAsync(appointment -> {
            List<BygSignInTemplate> signInTemplates = bygSignInTemplateService.list().parallelStream()
                    .sorted(Comparator.comparingInt(BygSignInTemplate::getWeight)).collect(Collectors.toList());
            Integer sitId = signInTemplates.parallelStream().findFirst().get().getId();
            Map<Integer, Integer> sitMap = signInTemplates.parallelStream()
                    .collect(Collectors.toMap(BygSignInTemplate::getId, BygSignInTemplate::getWeight));
            LambdaQueryWrapper<BygSignIn> siWrapper = Wrappers.lambdaQuery(BygSignIn.class)
                    .select(BygSignIn::getSitId).isNotNull(BygSignIn::getSitId).eq(BygSignIn::getAmId, appointment.getId());
            List<BygSignIn> signIns = bygSignInService.list(siWrapper);
            if (signIns.size() > 0) { // select from byg_sign_in
                Map<Integer, Long> siMap = signIns.parallelStream()
                        .collect(Collectors.groupingBy(BygSignIn::getSitId, Collectors.counting()));
                sitId = siMap.keySet().parallelStream().sorted((k1, k2) -> {
                    if (siMap.get(k1) == siMap.get(k2)) {
                        return sitMap.get(k2) - sitMap.get(k1);
                    } else return (int) (siMap.get(k2) - siMap.get(k1));
                }).findFirst().get();
            } else { // select from byg_sign_in_history
                LambdaQueryWrapper<BygSignInHistory> sitWrapper = Wrappers.lambdaQuery(BygSignInHistory.class)
                        .select(BygSignInHistory::getSitId).eq(BygSignInHistory::getAmId, appointment.getId());
                List<BygSignInHistory> signInHistories = bygSignInHistoryService.list(sitWrapper);
                if (signInHistories.size() > 0) {
                    Map<Integer, Long> siMap = signInHistories.parallelStream()
                            .collect(Collectors.groupingBy(BygSignInHistory::getSitId, Collectors.counting()));
                    sitId = siMap.keySet().parallelStream().sorted((k1, k2) -> {
                        if (siMap.get(k1) == siMap.get(k2)) {
                            return sitMap.get(k2) - sitMap.get(k1);
                        } else return (int) (siMap.get(k2) - siMap.get(k1));
                    }).findFirst().get();
                }
            }
            sitIdAr.set(sitId);
//            return bgAr.get().parallelStream().filter(bg -> bg.getType() == finalSitId).findFirst().get();
        }, threadPoolExecutor);

        CompletableFuture<Void> future2 = future0.thenAcceptAsync(appointment -> {
            LambdaQueryWrapper<BygPhoto> pWrapper = Wrappers.lambdaQuery(BygPhoto.class)
                    .eq(BygPhoto::getDmId, appointment.getDmId()).eq(BygPhoto::getIsFamily, true);
            BygPhoto photo = bygPhotoService.getOne(pWrapper);
            PhotoResDto photoResDto = new PhotoResDto().setWh(photo.getWh())
                    .setPhotoUrl(photo.getPhotoUrl()).setRemark(photo.getRemark());
            bgAr.get().parallelStream().forEach(bg -> {
                if (bg.getType() == MyEnum.BG_TYPE_FAMILY.getValue()) photoResDto.setPhotoBgUrl(bg.getUrl());
                if (bg.getType() == MyEnum.BG_TYPE_PHOTO_BR.getValue() &&
                        bg.getWh().equals(photo.getWh())) photoResDto.setPhotoBrUrl(bg.getUrl());
            });
            photoAr.set(photoResDto);
        }, threadPoolExecutor);

        CompletableFuture<Void> future3 = future0.thenAcceptAsync(appointment -> {
            BygAdmin reviewer = bygAdminService.getById(appointment.getReviewerId());
            LambdaQueryWrapper<BygAdmin> wrapper = Wrappers.lambdaQuery(BygAdmin.class).eq(BygAdmin::getUnitId, reviewer.getUnitId())
                    .isNotNull(BygAdmin::getTel).eq(BygAdmin::getRoleId, MyEnum.ROLE_MANAGER_ID.getValue()).last("LIMIT 1");
            telAr.set(bygAdminService.getOne(wrapper).getTel());
        }, threadPoolExecutor);

        CompletableFuture<BaseDataResDto<DmFinallyResDto>> future4 = CompletableFuture
                .allOf(future1,future2, future3).thenApplyAsync(x -> {
            BygBgTemplate sibg = bgAr.get().parallelStream()
                    .filter(bg -> bg.getType() == sitIdAr.get()).findFirst().get();
            DmFinallyResDto data = new DmFinallyResDto().setTel(telAr.get()).setPhoto(photoAr.get())
                    .setDmName(dmar.get().getName()).setDmGender(dmar.get().getGender())
                    .setSiBgUrl(sibg.getUrl()).setSiBgText(sibg.getText());
            bgAr.get().parallelStream().forEach(bg -> {
                if (bg.getType() == MyEnum.BG_TYPE_SINAME.getValue()) data.setSiNameUrl(bg.getUrl());
                if (bg.getType() == MyEnum.BG_TYPE_SICONTENT.getValue()) data.setSiContentUrl(bg.getUrl());
                if (bg.getType() == MyEnum.BG_TYPE_CBG.getValue()) data.setSiCbgUrl(bg.getUrl());
                if (bg.getType() == MyEnum.BG_TYPE_CBR.getValue()) data.setSiCbrUrl(bg.getUrl());
            });
            return new BaseDataResDto<DmFinallyResDto>(Status.SUCCESS).setData(data);
        });

        try {
            return future4.get();
        } catch (Exception e) {
            if (e.getMessage().contains(Status.PARAMETERERROR.getMessage()))
                return new BaseDataResDto<>(Status.PARAMETERERROR);
            return new BaseDataResDto<>(Status.ERROR);
        }

    }

}
